iT邦幫忙

2026 iThome 鐵人賽

DAY 23
0
JavaScript

JavaScript為什麼筆記本系列 第 23 篇

Day 23|function、function expression、arrow function 為什麼三種?

  • 分享至 

  • xImage
  •  

Day 22 我終於把一件以前「會用但沒真的想過」的事情補起來:

setCount(prev => prev + 1)

裡面的:

prev => prev + 1

不是什麼 React 專用語法。

它就是一個 function。

而且 JavaScript 的 function 本身可以是一個值,所以才能:

const addOne = number => number + 1

也能:

setCount(addOne)

或:

numbers.map(addOne)

到這裡好像很合理。

結果我往回看自己的程式,又看到三種長得很像的東西:

function add(a, b) {
  return a + b
}
const add = function (a, b) {
  return a + b
}

以及:

const add = (a, b) => {
  return a + b
}

甚至還可以縮成:

const add = (a, b) => a + b

然後我就卡住了。

如果它們最後都可以做到 add(1, 2),JavaScript 到底為什麼需要這麼多種?

難道真的只是:

舊寫法
↓
比較新的寫法
↓
比較潮的寫法

嗎?

好像不是。


先給 30 秒版本

今天先記這句:

Function declaration、function expression、arrow function 都能建立 function,但它們不是單純三種不同外觀;建立方式、可以出現的位置,以及部分行為都不完全一樣。

最粗略可以先這樣看:

function declaration
↓
直接宣告一個具名 function

function expression
↓
在 expression 裡產生一個 function value

arrow function
↓
也是 function expression
但語法更短,而且部分語意和傳統 function 不同

所以:

function add() {}

和:

const add = () => {}

不是單純把 function 換成 =>。


先看最像的三個版本

全部都做同一件事情:1 + 2。

Function declaration

function add(a, b) {
  return a + b
}

console.log(add(1, 2))

結果是 3。

Function expression

const add = function (a, b) {
  return a + b
}

console.log(add(1, 2))

也是 3。

Arrow function

const add = (a, b) => {
  return a + b
}

console.log(add(1, 2))

還是 3。

甚至:

const add = (a, b) => a + b

依然可以得到 3。

如果只做這個實驗,真的會得到一個很合理的錯覺:

三個根本一樣啊。

所以問題不能只問「它們能不能算 1 + 2」,而要問:

JavaScript 到底是用什麼方式建立這三個 function?


Function declaration:直接宣告一個 Function

先看第一種:

function add(a, b) {
  return a + b
}

這叫 Function Declaration。

可以先把它想成:

我要在這個 Scope 裡宣告一個叫 add 的 function。

概念上:

function add(...) {...}
        ↓
建立 function
        ↓
名稱 add 指向這個 function

這也是很多教學最先教的 function。

因為它很直白:

function calculateTotal(price, quantity) {
  return price * quantity
}

只看名字就知道這裡定義了一個叫 calculateTotal 的功能。


Function expression:先產生 Function,再把它當成值

再看:

const add = function (a, b) {
  return a + b
}

這裡其實可以拆成兩件事情。

右邊:

function (a, b) {
  return a + b
}

是一個 function value。

左邊:

const add =

則是在建立一個變數 binding。

所以概念上比較像:

建立一個 function value
↓
把它放進 add

這和 Day 22 的心智模型就接起來了。

例如:

const name = 'Anthony'

可以看成:

字串值
↓
放進 name

而:

const add = function (a, b) {
  return a + b
}

則是:

function value
↓
放進 add

Function expression 這個名字突然就比較有意思。

它是一個 expression,也就是會產生一個值的東西,而它產生的值剛好是 function。


Function Expression 不一定要有名字?

例如:

const add = function (a, b) {
  return a + b
}

function 後面沒有名字。

這是一個匿名 function expression。

但也可以寫成:

const add = function calculate(a, b) {
  return a + b
}

這時 function 本身有名字 calculate,而外面的變數仍然是 add。

今天先不深入這個名字到底在哪個 Scope 裡有效,不然我們會馬上掉進下一個坑。

先知道:

Function expression 可以產生具名或匿名的 function value。

就夠了。


Arrow function 其實也是 Expression

現在回頭看最常出現在 React 裡的:

const add = (a, b) => {
  return a + b
}

它叫 Arrow Function Expression。

注意最後那個 Expression。

它跟:

const add = function (a, b) {}

有一個重要共同點。

右邊都是在產生一個 function value。

所以:

const add = (a, b) => a + b

概念上也是:

建立一個 function value
↓
放進 add

這也解釋了為什麼可以直接:

numbers.map(number => number * 2)

因為:

number => number * 2

本身就會產生一個 function。

我甚至不需要先:

const double = number => number * 2

才能傳給 map。

可以直接:

建立 function
↓
立刻當 argument 傳給 map

這就是 Day 22 說的:

Function 是值。


Arrow Function 只是比較短嗎?

這是我以前最大的誤解之一。

例如:

function add(a, b) {
  return a + b
}

可以改成:

const add = (a, b) => {
  return a + b
}

又可以變成:

const add = (a, b) => a + b

所以真的很容易得到結論:

Arrow function 就只是 function 的短寫法。

但其實不是。

Arrow function 除了語法比較短之外,還有一些真正的行為差異。

例如它:

沒有自己的 this
沒有自己的 arguments
不能拿來當 constructor

也就是不能把:

function User(name) {
  this.name = name
}

無腦改成:

const User = name => {
  this.name = name
}

然後期待:

new User('Anthony')

照樣運作。

不會。

所以:

Arrow function 不是 traditional function 的單純短寫。

它有自己的語意。

這篇先不把 this 拆開,光 this 本身就足夠再寫一篇。


Arrow Function 為什麼在 Callback 裡這麼常見?

回頭看我們已經寫過的:

numbers.map(number => number * 2)

如果不用 arrow,也可以:

numbers.map(function (number) {
  return number * 2
})

兩個都可以。

但 callback 常常只是很小的一段邏輯:

拿到一個 number
↓
乘以 2
↓
回傳

寫:

number => number * 2

非常直接。

所以 arrow function 很常出現在 map、filter、find、setTimeout、Promise、React event、React state updater 這類需要 callback 的地方。

這裡的重点不是 Arrow 比 function 高級,而是:

我現在需要的是一個小型 function value,直接傳給另一個 API。

Arrow function 剛好非常適合這種寫法。


三種 Function 在「出現的位置」也不太一樣

Function declaration 看起來像一個獨立宣告:

function sayHello() {
  console.log('Hello')
}

Function expression 則可以出現在需要 value 的地方:

const sayHello = function () {
  console.log('Hello')
}

也能:

setTimeout(function () {
  console.log('Hello')
}, 1000)

Arrow function 一樣:

setTimeout(() => {
  console.log('Hello')
}, 1000)

因為 expression 可以產生值,所以 function expression 和 arrow function 才能直接被 setTimeout 這類 API 收到。


還有一個很怪的差別:誰可以先呼叫?

做一個小實驗。

Function declaration

sayHello()

function sayHello() {
  console.log('Hello')
}

Function 明明寫在下面,卻可以先呼叫。

再改成:

sayHello()

const sayHello = function () {
  console.log('Hello')
}

這次就不行。

Arrow function 也是:

sayHello()

const sayHello = () => {
  console.log('Hello')
}

也不行。

這又證明了一次:

三種寫法不只是長得不一樣。

但是今天我先不要直接丟一句「因為 hoisting」然後假裝事情講完了。

因為 Hoisting 本身又是一個超容易被教成「JavaScript 把程式碼搬到最上面」的東西。

今天只先留下觀察:

Function Declaration
可以出現宣告前呼叫的行為

const + Function Expression
不行

const + Arrow Function
也不行

原因之後再說。


那到底什麼時候用哪一種?

我現在不想把它背成 A 情境只能用 declaration、B 情境只能用 expression、C 情境只能用 arrow。

很多時候確實有不同寫法可以達到目的。

比較有用的思考方式是:

我要宣告一個清楚、具名、可重複使用的功能

function calculateTotal(price, quantity) {
  return price * quantity
}

Function declaration 很直觀。

我現在真的把 Function 當成一個值

const handler = function () {
  console.log('click')
}

Function expression 的心智模型很直接:

function value
↓
交給變數

我要傳一個短小的 Callback

items.map(item => item.name)

Arrow function 很自然。

除非那個 function 本身值得被命名和重複使用。


所以 React 為什麼到處都是 Arrow Function?

以前剛學 React 的時候,很容易出現一個錯覺:

React 的函式是不是都要寫 =>?

其實不是。

是因為 React 裡面非常常做:

把 function 當值傳出去

例如:

<button onClick={() => setCount(count + 1)}>

又或者:

setCount(prev => prev + 1)

再例如:

users.map(user => <UserCard user={user} />)

全部都需要一個 function value。

而 arrow function 寫這種小 callback 很方便。

不是 React 發明了 arrow function,而是 React 大量利用 JavaScript:

Function 可以當成值。


把 Day 17、21、22、23 接起來

Day 17:

onClick={handleClick}

為什麼不是:

onClick={handleClick()}

因為一個是在傳 function,一個是在現在就呼叫。

Day 21:

setCount(prev => prev + 1)

React 收到的是 updater function。

Day 22:

prev => prev + 1

為什麼 function 可以塞進另一個 function?

因為 function 本身就是 value。

Day 23:

function add() {}
const add = function () {}
const add = () => {}

為什麼又有這麼多種?

因為 JavaScript 提供不同方式建立 function,而它們的使用位置和部分語意並不完全相同。

現在這條線終於比較像一整塊 JavaScript,而不是 React 的一堆特殊規則了。


最容易搞錯的地方

誤解 1:Arrow Function 就是新版 Function

不是。

Arrow function 比較短,但它也有不同的語意,例如沒有自己的 this,也不能當 constructor。

誤解 2:Function Expression 一定是匿名 Function

不一定。

const fn = function namedFunction() {}

Function expression 也能有自己的名字。

誤解 3:三種寫法反正都一樣

如果只是輸入兩個數字、相加、return,看起來確實很像。

但宣告時機、this、arguments、constructor、expression context 都可能出現差異。

誤解 4:React 規定一定要用 Arrow Function

沒有。

React 只是很常需要 callback,而 arrow function 很適合建立短小的 callback value。


如果今天只記得一件事

Function declaration 是直接宣告 function;function expression 和 arrow function 則可以在 expression 裡產生 function value,而 arrow function 又具有和 traditional function 不完全相同的語意。

所以三種寫法不是古代、近代、現代三個版本,而是 JavaScript 提供不同建立 Function 的方式:基本功能有重疊,但使用位置與行為有部分差異。


下一個「為什麼」

不過今天寫到這裡,我發現一直偷偷出現一個詞:

Scope

像:

const add = function calculate() {}

calculate 到底在哪裡看得到?

如果我把:

const name = 'Anthony'

放進 function 裡,外面又為什麼看不到?

甚至:

if (true) {
  const count = 10
}

為什麼出了大括號就不能用了?

所以接下來想問的是:

Scope 到底在限制什麼?JavaScript 為什麼不能讓每個變數在任何地方都看得到?



上一篇
Day 22|setCount(prev => prev + 1) 裡面為什麼可以塞 function?
下一篇
Day 24|Scope 到底在限制什麼?為什麼有些變數出了大括號就消失了?
系列文
JavaScript為什麼筆記本 共 28 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言